/**
 * 深拷贝对于复杂引用数据类型，在堆内存中开辟了一块内存地址，将原有对象完全复制存放
 * 原理：
 *  将一个对象从内存中完整地拷贝出来一份给目标对象，并从堆内存中开辟一个全新的空间存放新对象，且新对象的修改并不会改变原对象，二者实现真正的分离。
 */

/**
 * 乞丐版 JSON.stringfy
 * JSON.stringfy() 是目前开发过程中最简单的深拷贝方法，
 * 就是把一个对象序列化成为 JSON 的字符串，并将对象里面的内容转换成字符串，
 * 最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象。
 * 注意：
 * 1.拷贝的对象如果有函数、undefined、symbol，JSON.stringify序列化后的字符串中这个键值对会消失；
 * 2.拷贝 Date 引用类型会变成字符串；
 * 3.无法拷贝不可枚举的属性；
 * 4.无法拷贝对象的原型链；
 * 5.拷贝 RegExp 引用类型会变成空对象；{}
 * 6.对象中含有 NaN、Infinity 以及 -Infinity，JSON序列化的结果会变成 null；
 * 7.无法拷贝对象的循环应用，即对象成环 (obj[key] = obj)。
 */


/**
 * 基础版（手写递归实现）
 * for in 遍历传入参数的属性值，
 * 如果值是引用类型则再次递归调用该函数，
 * 如果是基础数据类型就直接复制
 * 缺点：
 * 1.这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型；
 * 2.这种方法只是针对普通的引用类型的值做递归复制，而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝；
 * 3.对象的属性里面成环，即循环引用没有解决。
 */
// function deepClone(source) {
//   let cloneResult = Array.isArray(source) ? [] : {}
//   for (let prop in source) {
//     // prop是下标/key值
//     if (typeof source[prop] === 'object') {
//       cloneResult[prop] = deepClone(source[prop])
//     } else {
//       cloneResult[prop] = source[prop]
//     }
//   }
//   return cloneResult
// }

/**
 * 改进版（改进后递归实现）
 * 1.针对能够遍历对象的不可枚举属性以及 Symbol 类型，
     我们可以使用 Reflect.ownKeys 方法；
 * 2.当参数为 Date、RegExp 类型，则直接生成一个新的实例返回；
 * 3.利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的特性，
     顺便结合 Object 的 create 方法创建一个新对象，并继承传入原对象的原型链；
 * 4.利用 WeakMap 类型作为 Hash 表，因为 WeakMap 是弱引用类型，可以有效防止内存泄漏
     可以关注一下 Map 和 weakMap 的关键区别，这里要用 weakMap，
     作为检测循环引用很有帮助，如果存在循环，则引用直接返回 WeakMap 存储的值。
 */
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && typeof obj === 'null'
function deepClone(source, map = new WeakMap()) {
    // 解决Date、RegExp 类型
    if (source.constructor === Date) {
        return new Date(source)
    } else if (source.constructor === RegExp) {
        return new RegExp(source)
    }
    // map里面有就直接返回，解决循环引用
    if (map.has(source)) {
        return map.get(source)
    }

    // Object.getPrototypeOf返回指定对象的原型（内部[[Prototype]]属性的值）
    let reuslt = Object.create(Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source))
    // console.log(Object.getPrototypeOf(source));
    // console.log(Object.getOwnPropertyDescriptors(source));
    // Reflect.ownKeys,遍历对象的不可枚举属性以及 Symbol 类型
    for (let key in Reflect.ownKeys(source)) {
        reuslt[key] = (isComplexDataType(source[key]) && source[key] !== 'function') ? deepClone(source[key], map) : source[key]
    }
    return reuslt


}


let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: { name: '我是一个对象', id: 1 },
    arr: [0, 1, 2],
    func: function () { console.log('我是一个函数') },
    date: new Date(0),
    reg: new RegExp('/我是一个正则/ig'),
    [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', { enumerable: false, value: '不可枚举属性' });
/**
 * getOwnPropertyDescriptors(obj)
 * 遍历出每一个属性 value: 0, writable: true, enumerable: true, configurable: true 
 */
/**
 * Object.create相当于复制一个,这里主要是要继承原型链
 */
Object.create(obj, Object.getOwnPropertyDescriptors(obj))
let target = deepClone(obj)
console.log(target);